﻿#region Header
// ------------------------ Licence / Copyright ------------------------
// 
// ScrumTable for Scrum Meeting Support
// Copyright © HSR - Hochschule für Technik Rapperswil
// All Rights Reserved
// 
// Author:
//  Michael Gfeller Silvan Gehrig Patrick Boos
// 
// ---------------------------------------------------------------------
#endregion

#region Usings

using System;
using System.Collections.Generic;
using System.Linq;

using ScrumTable.Common;
using ScrumTable.Common.Collections;
using ScrumTable.Common.Data;
using ScrumTable.DL.Data.Schema;

#endregion

namespace ScrumTable.DL.Data.Generic.Cache
{
    /// <summary>
    /// This class wraps a team foundation field definition instance.
    /// </summary>
    public class CacheFieldDefinition : CacheElementBase
    {
        #region Declarations
        //--------------------------------------------------------------------
        // Declarations
        //--------------------------------------------------------------------

        #endregion

        #region Properties
        //--------------------------------------------------------------------
        // Properties
        //--------------------------------------------------------------------

        /// <summary>
        /// Gets the allowed values for this field.
        /// </summary>
        public IEnumerable<object> AllowedValues { get; private set; }

        /// <summary>
        /// Gets the field content type which has been provided by the underlying
        /// backend system.
        /// </summary>
        public Type FieldType { get; private set; }

        /// <summary>
        /// Gets the type of the field provided by the back end system.
        /// </summary>
        public DataType FieldTypeFormat { get; private set; }

        /// <summary>
        /// Gets the first (default) allowed value for this field.
        /// </summary>
        public object DefaultValue { get; private set; }

        /// <summary>
        /// Gets the suggested values for this field.
        /// </summary>
        public IEnumerable<object> SuggestedValues { get; private set; }

        /// <summary>
        /// Gets the associated schema instance to the current field. This property
        /// may countain a null pointer if no schema is available.
        /// </summary>
        public SchemaBase Schema { get; private set; }

        /// <summary>
        /// Specifies the display name of the current field.
        /// </summary>
        public string DisplayName { get; private set; }

        /// <summary>
        /// Specifies that the schema has injected the current field and it's
        /// not defined in the underlying back end system.
        /// </summary>
        public bool IsSchemaInjected
        {
            get;
            private set;
        }

        /// <summary>
        /// Returns the type which is specified for/from the backend system.
        /// </summary>
        public Type BackEndFieldType { get; private set; }

        /// <summary>
        /// Returns true if the corresponding field definition must follow to
        /// a set of values.
        /// </summary>
        public bool HasAllowedValues { get { return !AllowedValues.IsEmpty(); } }

        /// <summary>
        /// Returns true if a (not empty) value is required.
        /// </summary>
        private bool BackEndIsValueRequired { get { return (DefaultValue != null && !BackEndAllowedValues.IsEmpty()); } }

        /// <summary>
        /// Returns the allowed values which are specified for/from the backend system.
        /// </summary>
        private IEnumerable<object> BackEndAllowedValues { get; set; }

        #endregion

        #region Constructors / Destructor
        //--------------------------------------------------------------------
        // Constructors / Destructor
        //--------------------------------------------------------------------

        /// <summary>
        /// Creates a new CacheFieldDefinition instance. This constructor is used in
        /// the current library only and creates a field definition which has
        /// a back end field definition.
        /// </summary>
        internal CacheFieldDefinition(string unqiueId, int id, string displayName, SchemaBase fieldSchema, DataType type, IEnumerable<object> allowedValues, object defaultValue)
            : base(unqiueId, id, fieldSchema.SchemaName, CacheIdentity.DefaultRevision)
        {
            PreCondition.AssertNotNull(fieldSchema, "fieldSchema");

            Schema = fieldSchema;
            FieldTypeFormat = type;
            DefaultValue = defaultValue;
            DisplayName = (displayName ?? fieldSchema.SchemaDisplayName);

            BackEndAllowedValues = (allowedValues ?? new object[0]);
            BackEndId = fieldSchema.SchemaMappedName;
            BackEndFieldType = DataTypeConverter.ToClassType(type);

            AllowedValues = EvaluateAllowedValues();
            SuggestedValues = HasAllowedValues ? AllowedValues : (fieldSchema.SchemaSuggestedValues ?? new object[0]);
            FieldType = (Schema.HasSchemaValueType) ? Schema.SchemaType : BackEndFieldType;

            ValidateDataTypes();
        }

        /// <summary>
        /// Creates a new CacheFieldDefinition instance. This constructor is used in
        /// the current library only and creates a virtual field definition which
        /// must be stored into the settings.
        /// </summary>
        internal CacheFieldDefinition(string unqiueId, SchemaBase fieldSchema)
            : base(unqiueId, CacheIdentity.DefaultId, fieldSchema.SchemaName, CacheIdentity.DefaultRevision)
        {
            PreCondition.AssertNotNull(fieldSchema, "fieldSchema");

            Schema = fieldSchema;
            FieldTypeFormat = (!fieldSchema.IsSchemaReference) ? DataTypeConverter.ToDataType(fieldSchema.SchemaType) : DataType.PlainText;
            DefaultValue = fieldSchema.SchemaDefaultValue;
            DisplayName = fieldSchema.SchemaName;

            BackEndAllowedValues = (fieldSchema.SchemaAllowedValues ?? new object[0]);
            BackEndId = fieldSchema.SchemaMappedName;
            BackEndFieldType = Schema.SchemaType;
            IsSchemaInjected = true;

            AllowedValues = EvaluateAllowedValues();
            SuggestedValues = HasAllowedValues ? AllowedValues : (fieldSchema.SchemaSuggestedValues ?? new object[0]);
            FieldType = (Schema.HasSchemaValueType) ? Schema.SchemaType : BackEndFieldType;

            ValidateDataTypes();
        }

        /// <summary>
        /// Creates a new CacheFieldDefinition instance. This constructor is used in
        /// the current library only and creates a virtual field definition which
        /// must be stored into the settings.
        /// </summary>
        /// <remarks>
        /// This constructor is currently used for Report meta fields.
        /// </remarks>
        protected CacheFieldDefinition(
            string unqiueId,
            string name,
            string displayName,
            DataType dataFormat,
            Type type,
            object defaultValue,
            IEnumerable<object> allowedValues = null,
            IEnumerable<object> suggestedValues = null)
            : base(unqiueId, CacheIdentity.DefaultId, name, CacheIdentity.DefaultRevision)
        {
            PreCondition.AssertNotNull(type, "type");

            DefaultValue = defaultValue;
            DisplayName = displayName ?? string.Empty;
            FieldTypeFormat = dataFormat;
            FieldType = DataTypeConverter.ToClassType(dataFormat);

            BackEndAllowedValues = (allowedValues ?? new object[0]);
            BackEndId = name;
            BackEndFieldType = type;

            AllowedValues = BackEndAllowedValues;
            SuggestedValues = HasAllowedValues ? AllowedValues : (suggestedValues ?? new object[0]);

            ValidateDataTypes();
        }

        #endregion

        #region Methods
        //--------------------------------------------------------------------
        // Methods
        //--------------------------------------------------------------------

        /// <summary>
        /// Determines if the given value is valid for the current field.
        /// </summary>
        /// <param name="toCheck">Specifies the value to check.</param>
        /// <returns>Returns true if the given value is true.</returns>
        public bool IsValidBackEndValue(object toCheck)
        {
            return (!BackEndIsValueRequired || BackEndAllowedValues.IsEmpty() || BackEndAllowedValues.Contains(toCheck));
        }

        /// <summary>
        /// Creates a new clone instance of the current object.
        /// </summary>
        /// <returns>Returns the created clone instance.</returns>
        protected override CacheElementBase CreateCloneInstance()
        {
            return new CacheFieldDefinition(UniqueId, Id, DisplayName, Schema, FieldTypeFormat, BackEndAllowedValues, DefaultValue);
        }

        private void ValidateDataTypes()
        {
            foreach (var allowedValue in AllowedValues)
            {
                if (!BackEndFieldType.IsInstanceOfType(allowedValue))
                {
                    throw new CacheFieldDefinitionInvalidTypeException(allowedValue, "AllowedValues", DisplayName, BackEndFieldType);
                }
            }

            foreach (var backendAllowedValue in BackEndAllowedValues)
            {
                if (!BackEndFieldType.IsInstanceOfType(backendAllowedValue))
                {
                    throw new CacheFieldDefinitionInvalidTypeException(backendAllowedValue, "BackEndAllowedValues", DisplayName, BackEndFieldType);
                }
            }

            foreach (var suggestedValue in SuggestedValues)
            {
                if (!BackEndFieldType.IsInstanceOfType(suggestedValue))
                {
                    throw new CacheFieldDefinitionInvalidTypeException(suggestedValue, "SuggestedValues", DisplayName, BackEndFieldType);
                }
            }

            if (DefaultValue != null && !BackEndFieldType.IsInstanceOfType(DefaultValue))
            {
                throw new CacheFieldDefinitionInvalidTypeException(DefaultValue, "DefaultValue", DisplayName, BackEndFieldType);
            }
        }

        private IEnumerable<object> EvaluateAllowedValues()
        {
            if (Schema.HasSchemaAllowedValues)
            {
                if (BackEndAllowedValues.IsEmpty() || Schema.IsValidSchemaValueMapping(BackEndAllowedValues.Convert<string>(o => o.ToString())))
                {
                    return Schema.SchemaAllowedValues;
                }
            }
            return BackEndAllowedValues;
        }

        #endregion

        #region Events
        //--------------------------------------------------------------------
        // Events
        //--------------------------------------------------------------------

        #endregion
    }



    /// <summary>
    /// This class wraps a team foundation field definition instance.
    /// </summary>
    public class CacheFieldDefinition<T> : CacheFieldDefinition
        where T : class
    {
        #region Declarations
        //--------------------------------------------------------------------
        // Declarations
        //--------------------------------------------------------------------

        #endregion

        #region Properties
        //--------------------------------------------------------------------
        // Properties
        //--------------------------------------------------------------------

        /// <summary>
        /// Gets the allowed values for this field.
        /// </summary>
        public new IEnumerable<T> AllowedValues { get { return base.AllowedValues.Convert<T>(); } }

        /// <summary>
        /// Gets the first (default) allowed value for this field.
        /// </summary>
        public new T DefaultValue { get { return ObjectUtil.ChangeType<T>(base.DefaultValue); } }

        #endregion

        #region Constructors / Destructor
        //--------------------------------------------------------------------
        // Constructors / Destructor
        //--------------------------------------------------------------------

        /// <summary>
        /// Creates a new CacheFieldDefinition instance. This constructor is used in
        /// the current library only and creates a virtual field definitions (for
        /// example for the reporting fields).
        /// </summary>
        /// <remarks>
        /// This constructor is currently used for Report meta fields.
        /// </remarks>
        internal CacheFieldDefinition(string unqiueId, string name, string displayName, DataType dataFormat, 
            T defaultValue, IEnumerable<T> allowedValues = null, IEnumerable<T> suggestedValues = null)
            : base(unqiueId, name, displayName, dataFormat, typeof(T), defaultValue, allowedValues, suggestedValues)
        {
        }

        #endregion

        #region Methods
        //--------------------------------------------------------------------
        // Methods
        //--------------------------------------------------------------------

        /// <summary>
        /// Determines if the given value is valid for the current field.
        /// </summary>
        /// <param name="toCheck">Specifies the value to check.</param>
        /// <returns>Returns true if the given value is true.</returns>
        public bool IsValidBackEndValue(T toCheck)
        {
            return base.IsValidBackEndValue(toCheck);
        }

        #endregion

        #region Events
        //--------------------------------------------------------------------
        // Events
        //--------------------------------------------------------------------

        #endregion
    }
}